fix(renderer3d): populate Object3D.vertices for static batch (#630) - #664
Conversation
Models and model instances were created with empty `vertices` fields, causing the static batch rebuild to produce zero vertices while static objects were excluded from the dynamic render pass -- making them invisible when `SetModelStatic(true)` was called. Primitives already populated this field correctly, which is why `SetObjectStatic` on primitives worked. This fix applies the same pattern to models and model instances (non-skinned only, since skinned models use a 16-FPV layout incompatible with the 8-FPV static batch). Fixes #630 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add test verifying that primitives marked static still render via the static batch path. Also add clarifying comment on why skinned models keep an empty vertices vec (16 FPV layout incompatible with 8 FPV static batch format). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Claude finished @aram-devdocs's task in 2m 9s —— View job Code Review — fix(renderer3d): populate Object3D.vertices for static batch (#630)
Verdict: APPROVED ✅The root cause diagnosis is accurate, the fix is minimal and correct. No blockers. BLOCKERSNone. WARNINGS
W1 detail: The PR description says primitives already worked because W2 detail: Since the cube is the only object in the test and it's marked static, a precise assertion would be W3 detail: In bind_pose_vertices.push(verts.clone()); // line 114 — pre-existing, used only for CPU-skinned animation
// ...
vertices: if is_skinned { Vec::new() } else { verts.clone() }, // line 135 — new
Positive Callouts
|
Rust cross-compilation for Android takes ~12min without cache hits, leaving insufficient time for both Gradle builds within the 15min timeout. Matches the pattern from the iOS timeout fix (8a6ddd5). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Overview
Type: fix
Summary:
Fix
SetModelStatic(true)making models invisible by populatingObject3D.verticesfor non-skinned models and instances. The static batch rebuild reads CPU-side vertex data from this field, but it was alwaysVec::new()for models (only primitives populated it correctly).Related Issues: Fixes #630
Changes Made
Engine Core (
goud_engine/src/)libs/graphics/renderer3d/core_models/mod.rs: PopulateObject3D.verticeswithverts.clone()for non-skinned models (skinned models use 16 FPV layout incompatible with 8 FPV static batch)libs/graphics/renderer3d/core_model_instances.rs: Clone vertices from source object when instantiatinglibs/graphics/renderer3d/tests.rs: Add regression test verifying static primitives render via batch pathFFI Layer (
goud_engine/src/ffi/)No changes
C# SDK (
sdks/csharp/)No changes
Python SDK (
sdks/python/)No changes
TypeScript SDK (
sdks/typescript/)No changes
Codegen Pipeline (
codegen/)No changes
Proc Macros (
goud_engine_macros/)No changes
Tools (
tools/)No changes
WASM (
goud_engine/src/wasm/)No changes
Examples (
examples/)No changes
Documentation
No changes
Architectural Compliance
Testing
cargo testpasses (pre-existingnative_main_threadGPU test failure unrelated)cargo clippy -- -D warningsis cleancargo fmt --all -- --checkpassesCode Quality
todo!()orunimplemented!()in production code#[allow(unused)]without justification commentResult, notunwrap()/expect()in library codeDocumentation
Breaking Changes
None
Version Bump
Bump type: patch
Justification: Bug fix for static model rendering
Security
unsafeblocksPerformance
Non-skinned models now retain a CPU-side copy of vertex data in
Object3D.vertices(same pattern primitives already use). This trades additional memory for correct static batching. For large scenes with many static models, the static batch reduces draw calls significantly — a net performance win at the cost of ~1x vertex memory per non-skinned model.Deployment
Reviewer Notes
Root cause:
rebuild_static_batch()readsobj.verticesto bake world-space transforms into a single VBO. Models and instances were created withvertices: Vec::new(), producing zero vertices in the batch while being excluded from the dynamic render pass. Primitives worked becausecreate_primitive()already populated this field.